#include "iopcsclite.h"

//#ifdef WIN32
//#undef UNICODE
//static char *pcsc_stringify_error(LONG rv)
//{
//    static char out[20];
//    sprintf_s(out, sizeof(out), "0x%08X", rv);
//    return out;
//}
//#endif

//#ifdef __APPLE__
//#include <PCSC/winscard.h>
//#include <PCSC/wintypes.h>
//#else
//#include <winscard.h>
//#endif

/// notify r/w errors at the selected sc reader or smart card
/// f   : called function
/// rv  : error message
#define CHECK(f, rv) \
    if (SCARD_S_SUCCESS != rv) { \
        printf(RED f " : %s\n" RESET, pcsc_stringify_error(rv)); \
    return -1; \
    }

LONG                rv;
SCARDCONTEXT        hContext;           /// Returned reference to selected connection
LPTSTR              mszReaders;
SCARDHANDLE         hCard = -1;
DWORD               dwReaders,
                    dwActiveProtocol,
                    dwRecvLength;       /// Length of the response.
SCARD_IO_REQUEST    pioSendPci;
LPTSTR              targetReader;
BYTE                pbRecvBuffer[258];  /// Response from the card.
/*
BYTE                fc = 0x40;          /// filled character
BYTE                sz = 0x50;          /// size of register into morpho card (data to write)

/// select app from Morpho card
BYTE                selAppApdu[]        = { 0x00, 0xA4, 0x04, 0x00, 0x07, fc, fc, fc, fc, fc, fc, fc, fc };
/// application id to select
BYTE                appId[]             = { 0xA0, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x00 };
/// read apdu
BYTE                readApdu[]          = { 0x00, 0xB2, 0x01, 0x01, 0x00, sz };
/// get response apdu
BYTE                getResponseApdu[]   = { 0x00, 0xC0, 0x00, 0x00, sz };
*/
void    hexPrintFromByteArray(size_t p, const BYTE array[], size_t sz)
{
    size_t i;
    for(i = 0; i < sz; i++)
    {
        if ((i == p) && (p != 0)) printf("\n");
        printf(YELLOW "%02X " RESET, array[i]);
    }
    printf("\n");
}

void    hexPrintFromCharArray(const char charArray[], size_t len)
{
    size_t i;
    for (i = 0; i < len; i++)
        printf("%02X ", charArray[i]);
    printf("\n");
}

void    charPrintOnly(const BYTE array[], BYTE fC)
{
    size_t i;
    for(i=0; array[i] != fC; i++)
        printf(CYAN "%c  " RESET, array[i]);        /// char message
    printf("\n");
}

/// \brief  This function creates a communication context to the PC/SC Resource Manager.
///         This must be the first function called in a PC/SC application.
/// \param  hContext: Returned reference to this connection
/// \return SCARD_S_SUCCESS         : Successful
///         SCARD_E_INVALID_VALUE   : Invalid scope type passed
LONG    sCardEstablishContext()
{
    rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
    printf("hContext SCEC = %ld\n", hContext);
//    printf("rv = %ld\n", rv);
    CHECK("SCardEstablishContext", rv);
    return rv;
}

LONG    sCardListReaders(LPTSTR &mszReaders)
{
    /*!
     * If value pointed by dwReaders is specified as SCARD_AUTOALLOCATE, then mszReaders is casted as a pointer to a pointer,
     * and receives the address of a block of memory containing the character string, allocated by the PC/SC API.
     * This block of memory must be deallocated by the calling application with SCardFreeMemory.
     * On success, mszReaders is a multi-string and separated by a null character ('\0') and ended by a double null character
     * (e.g. "Reader A\0Reader B\0\0").
     */
    dwReaders = SCARD_AUTOALLOCATE;
    /// \brief  This function returns a list of currently available readers on the system. mszReaders is a pointer to a
    ///         character string which will be allocated by the application.
    /// \param  mszReaders  : Multi-string with list of readers.
    /// \param  dwReaders   : Size of multi-string buffer including NULL’s.
    /// \return SCARD_S_SUCCESS             : Successful
    ///         SCARD_E_INVALID_HANDLE      : Invalid Scope Handle
    ///         SCARD_E_INSUFFICIENT_BUFFER : Reader buffer not large enough
    ///         SCARD_E_READER_UNAVAILABLE  : No readers available.
    printf("hContext SCLR = %ld\n", hContext);
    rv = SCardListReaders(hContext, NULL, (LPTSTR)&mszReaders, &dwReaders);
//    printf("rv = %ld\n", rv);
    CHECK("SCardListReaders", rv);
    return rv;
}

LONG    sCardConnect(const char* targetReader)
{
    LONG    rv;
    rv = SCardConnect(hContext,                                 /// IN, Connection context to the PC/SC Resource Manager.
                      targetReader,                             /// IN, Reader name to connect to, first one by default.
                      SCARD_SHARE_SHARED,                       /// IN, Mode of connection type: exclusive or shared.
                      SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,    /// IN, Desired protocol use.
                      &hCard,                                   /// OUT, Handle to this connection.
                      &dwActiveProtocol                         /// OUT, Established protocol to this connection.
                      );
//    printf("rv = %ld\n", rv);
    printf("hContext SCC  = %ld | hCard SCC = %lu\n", hContext, hCard);
    /// hCard == 0 then CHECK throws error.
    CHECK("SCardConnect", rv);

    switch(dwActiveProtocol)
    {
    case SCARD_PROTOCOL_T0:                 /// response
        pioSendPci = *SCARD_PCI_T0;         /// definition
        break;

    case SCARD_PROTOCOL_T1:
        pioSendPci = *SCARD_PCI_T1;
        break;
    }

    return rv;
}

long    sCardTransmit(const unsigned char apdu[], size_t szApdu, unsigned char recvBuffer[], unsigned long &recvLength)
{
    printf("hContext SCT  = %ld | hCard SCT = %lu\n", hContext, hCard);
    LONG    rv = SCardTransmit(hCard,               /// IN      Connection made from SCardConnect.
                               &pioSendPci,         /// INOUT   Structure of protocol information.
                               apdu,          /// IN      APDU to send to the card.
                               szApdu, //sizeof(apdu),  /// IN      Length of the APDU.
                               NULL,                /// INOUT   Structure of protocol information. (NULL) possible.
                               recvBuffer,        /// OUT     Response from the card.
                               &recvLength        /// INOUT   Length of the response.
                               );
//    printf("rv = %ld\n", rv);
    CHECK("SCardTransmit", rv);
    hexPrintFromByteArray(recvLength-2, recvBuffer, recvLength);
    printf(MAGENTA "----------------------------------------------\n" RESET);
    if (recvBuffer[0] == (BYTE)0x6A && recvBuffer[1] == (BYTE)0x82) return -1;  /// 6A 82 error code
    if (recvBuffer[0] == (BYTE)0x6D && recvBuffer[1] == (BYTE)0x00) return -1;  /// 6D 00 error code
    return rv;
}

long    sCardDisconnect()
{
    LONG    rv = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
//    printf("rv = %ld\n", rv);
    CHECK("SCardDisconnect", rv);
    return rv;
}

long    sCardFreeMemory()
{
    /// free memory
    LONG    rv = SCardFreeMemory(hContext, mszReaders);
//    printf("rv = %ld\n", rv);
    CHECK("SCardFreeMemory", rv);
    return rv;
}

long    sCardReleaseContext()
{
    LONG    rv = SCardReleaseContext(hContext);
//    printf("rv = %ld\n", rv);
    CHECK("SCardReleaseContext", rv);
    return rv;
}
